/****************************************************************************
 * Copyright (c) 2008 Versant Corp.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     Markus Kuppe (mkuppe <at> versant <dot> com) - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.internal.remoteservices.ui.handlers;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.ecf.core.IContainer;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.internal.remoteservices.ui.RemoteServiceHandlerUtil;
import org.eclipse.ecf.remoteservice.IRemoteCall;
import org.eclipse.ecf.remoteservice.IRemoteCallListener;
import org.eclipse.ecf.remoteservice.IRemoteService;
import org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter;
import org.eclipse.ecf.remoteservice.IRemoteServiceReference;
import org.eclipse.ecf.remoteservice.events.IRemoteCallCompleteEvent;
import org.eclipse.ecf.remoteservice.events.IRemoteCallEvent;
import org.eclipse.ecf.remoteservices.ui.MethodInvocationDialog;
import org.eclipse.equinox.concurrent.future.IFuture;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class ReflectiveRemoteServiceHandler extends AbstractHandler implements IHandler {

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.
	 * commands .ExecutionEvent)
	 */
	public Object execute(ExecutionEvent event) throws ExecutionException {
		final String clazz = event
				.getParameter("org.eclipse.ecf.remoteservices.ui.commands.reflectiveMethodDialogParameter"); //$NON-NLS-1$

		final IRemoteServiceContainerAdapter adapter = RemoteServiceHandlerUtil
				.getActiveIRemoteServiceContainerAdapterChecked(event);
		if (adapter == null) {
			MessageDialog.openError(null, "Handler invocation failed", //$NON-NLS-1$
					"No container found"); //$NON-NLS-1$
			return null;
		}

		final IRemoteServiceReference[] references = RemoteServiceHandlerUtil
				.getActiveIRemoteServiceReferencesChecked(event);
		if (references == null || references.length == 0) {
			MessageDialog.openError(null, "Handler invocation failed", //$NON-NLS-1$
					"No remote service reference found"); //$NON-NLS-1$
			return null;
		}

		final IRemoteService remoteService = adapter.getRemoteService(references[0]);
		if (remoteService == null) {
			MessageDialog.openError(null, "Handler invocation failed", //$NON-NLS-1$
					"No remote service found"); //$NON-NLS-1$
			return null;
		}

		try {
			executeMethodInvocationDialog(Class.forName(clazz), remoteService);
		} catch (ClassNotFoundException e) {
			MessageDialog.openError(null, "Handler invocation failed", //$NON-NLS-1$
					e.getLocalizedMessage());
			throw new ExecutionException(e.getMessage(), e);
		}
		return null;
	}

	protected void executeMethodInvocationDialog(final Class cls, final IRemoteService remoteService) {
		final MethodInvocationDialog mid = new MethodInvocationDialog((Shell) null, cls);
		if (mid.open() == Window.OK) {
			final int timeout = (mid.getTimeout() > 0) ? mid.getTimeout() : 30000;
			final String methodName = mid.getMethod().getName();
			final Object[] methodArgs = mid.getMethodArguments();
			final IRemoteCall remoteCall = new IRemoteCall() {
				public String getMethod() {
					return methodName;
				}

				public Object[] getParameters() {
					return methodArgs;
				}

				public long getTimeout() {
					return timeout;
				}
			};
			final int invokeType = mid.getInvocationType();
			try {
				switch (invokeType) {
				case MethodInvocationDialog.ASYNC_FIRE_AND_GO:
					remoteService.callAsync(remoteCall);
					break;
				case MethodInvocationDialog.ASYNC_FUTURE_RESULT:
					invokeFuture(cls, remoteService, remoteCall);
					break;
				case MethodInvocationDialog.ASYNC_LISTENER:
					invokeAsyncListener(cls, remoteService, remoteCall);
					break;
				case MethodInvocationDialog.OSGI_SERVICE_PROXY:
					throw new UnsupportedOperationException();
				// invokeOSGiProxy(interfaceClass, remoteCall);
				// break;
				case MethodInvocationDialog.REMOTE_SERVICE_PROXY:
					throw new UnsupportedOperationException();
				// invokeProxy(interfaceClass, remoteService, remoteCall);
				// break;
				case MethodInvocationDialog.SYNCHRONOUS:
					invokeSync(cls, remoteService, remoteCall);
					break;
				default:
					break;
				}
			} catch (final Exception e) {
				showException(e);
			}
		}
	}

	protected void showException(final Throwable t, final IContainer container, ID targetID) {
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				String msg = t.toString();
				if (t.getCause() != null) {
					msg += t.getCause().toString();
				}
				MessageDialog.openInformation(null, "Received Exception", //$NON-NLS-1$
						NLS.bind("Exception: {0}", msg)); //$NON-NLS-1$
				container.disconnect();
			}
		});
	}

	protected void invokeFuture(Class cls, IRemoteService remoteService, IRemoteCall remoteCall)
			throws InterruptedException, InvocationTargetException, OperationCanceledException {
		// Make async call with future result
		final IFuture asyncResult = remoteService.callAsync(remoteCall);
		// Call blocking get and show result
		showResult(cls.getName(), remoteCall, asyncResult.get());
	}

	private void invokeAsyncListener(final Class interfaceClass, final IRemoteService remoteService,
			final IRemoteCall remoteCall) {
		// Make async call
		remoteService.callAsync(remoteCall, new IRemoteCallListener() {
			public void handleEvent(IRemoteCallEvent event) {
				if (event instanceof IRemoteCallCompleteEvent) {
					final IRemoteCallCompleteEvent complete = (IRemoteCallCompleteEvent) event;
					if (complete.hadException()) {
						showException(complete.getException());
					} else
						showResult(interfaceClass.getName(), remoteCall, complete.getResponse());
				}
			}
		});
	}

	private void invokeSync(final Class interfaceClass, final IRemoteService remoteService,
			final IRemoteCall remoteCall) {
		try {
			Object callSync = remoteService.callSync(remoteCall);
			showResult(interfaceClass.getName(), remoteCall, callSync);
		} catch (ECFException e) {
			showException(e);
		}
	}

	protected void showException(final Throwable t) {
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				String msg = t.toString();
				if (t.getCause() != null) {
					msg += t.getCause().toString();
				}
				MessageDialog.openInformation(null, "Received Exception", //$NON-NLS-1$
						NLS.bind("Exception: {0}", msg)); //$NON-NLS-1$
			}
		});
	}

	protected void showResult(final String serviceInterface, final IRemoteCall remoteCall, final Object result) {
		final Object display = (result != null && result.getClass().isArray()) ? Arrays.asList((Object[]) result)
				: result;
		final Object[] bindings = new Object[] { serviceInterface, remoteCall.getMethod(),
				Arrays.asList(remoteCall.getParameters()), display };
		Display.getDefault().asyncExec(new Runnable() {
			public void run() {
				MessageDialog.openInformation(null, "Received Response", //$NON-NLS-1$
						NLS.bind("Service Interface:\n{0}\n\nMethod: {1}\nParameters: {2}\n\nResult:  {3}", //$NON-NLS-1$
								bindings));
			}
		});
	}
}
